package org.example.codesandbox;

import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.example.codesandbox.model.ExecuteCodeRequest;
import org.example.codesandbox.model.ExecuteCodeResponse;
import org.example.codesandbox.model.JudgeInfo;
import org.example.codesandbox.model.ProcessMessage;
import org.example.codesandbox.utils.ProcessUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/*
* 代码沙箱模板方法
* */
@Slf4j
public abstract class  JavaCodeSandBoxTemplate implements CodeSanBox{


    private static final String GLOBAL_CODE_DIR_NAME = "tmpDir";//存放代码的目录名

    private static final String CLASS_NAME = "Main.java";//类名

    private static final long TIME_OUT = 5000L;//超时时间 防止提交代码设置睡眠卡死

    /*
    @param code 用户提交的代码
    @return 返回文件
    * 1,把用户的代码保存为文件
    * */
    public File saveCodeFile(String code){
        String userDir = System.getProperty("user.dir");//获取用户的当前工作目录
        String globalCodePath = userDir + File.separator + GLOBAL_CODE_DIR_NAME;//全局代码存放路径

        //判断路径目录是否存在
        if (!FileUtil.exist(globalCodePath)) {
            //不存在则创建
            FileUtil.mkdir(globalCodePath);
        }
        //创建一个代码隔离的目录
        String userCodePatternPath = globalCodePath + File.separator + UUID.randomUUID();
        //提交代码所在的路径
        String userCodePath = userCodePatternPath + File.separator + CLASS_NAME;

        //把代码放入userCodePath中的Main类中
        System.out.println("提交的代码为：" + code);
        File file = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
        return file;
    }

    /*
    * 2，编译文件
    * */
    public ProcessMessage compileFile(File file){
        //编译代码
        //拼接编译命令
        String compileCmd = String.format("javac -encoding utf-8 %s", file.getAbsoluteFile());
        //编译文件
        try {
            Process process = Runtime.getRuntime().exec(compileCmd);//返回得到一个进程
            ProcessMessage compileProcessMessage = ProcessUtils.process(process, "编译");
            return compileProcessMessage;
        } catch (Exception e) {
            throw new RuntimeException("编译代码失败");
        }
    }

    /*
    * 3,运行代码得到输出结果
    * */
    public List<ProcessMessage> runFile(File file,List<String> inputList){
        //运行文件
        List<ProcessMessage> processMessageList = new ArrayList<>(); //执行结果的集合
        for (String input : inputList) {
            //-Xmx256m 最大堆内存 256m防止提交的代码死循环创建对象占用内存资源
            String runCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s Main %s", file.getParentFile().getAbsolutePath(), input);
            try {
                Process process = Runtime.getRuntime().exec(runCmd);
                new Thread(() -> {
                    //超时控制
                    try {
                        Thread.sleep(TIME_OUT);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    process.destroy();
                }).start();
                ProcessMessage runProcessMessage = ProcessUtils.process(process, "运行");
                processMessageList.add(runProcessMessage);
            } catch (IOException e) {
                throw new RuntimeException("运行代码失败" + e.getMessage());
            }
        }
        return processMessageList;
    }

    /*
    * 4,收集整理输出结果
    * */
    public ExecuteCodeResponse getOutput(List<ProcessMessage> processMessageList){
        List<String> outputList = new ArrayList<>(); //输出结果的集合
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse(); //返回结果
        long maxTime = 0L;
        for (int i = 0; i < processMessageList.size(); i++) {
            if (processMessageList.get(i).getErrorMessage() != null) {
                executeCodeResponse.setStatus(3);//返回失败的状态
                break;
            }
            outputList.add(processMessageList.get(i).getMessage());
            maxTime = Math.max(processMessageList.get(i).getTime(), maxTime);
        }
        if (outputList.size() == processMessageList.size()) {
            executeCodeResponse.setStatus(2);//返回成功的状态
        }
        executeCodeResponse.setOutput(outputList);
//        executeCodeResponse.setMessage();
        JudgeInfo judgeInfo = new JudgeInfo();
        System.out.println("最大运行时间：" + maxTime);
        judgeInfo.setTime(maxTime);
//        judgeInfo.setMemory();
        executeCodeResponse.setJudgeInfo(judgeInfo);
        return executeCodeResponse;
    }

    /*
    * 5,清理文件
    * */
    public boolean deleteFile(File file) {
        //清理文件
        if (file.getParentFile() != null) {
            boolean del = FileUtil.del(file.getParentFile().getAbsolutePath());
            System.out.println("删除" + (del ? "成功" : "失败"));
            return del;
        }
        return true;
    }


    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
        List<String> input = executeCodeRequest.getInput();
        String language = executeCodeRequest.getLanguage();
        String code = executeCodeRequest.getCode();

        //1,把用户的代码保存为文件
        File file = saveCodeFile(code);

        //2,编译代码，得到class文件
        ProcessMessage processMessage = compileFile(file);
        System.out.println(processMessage);

        //3,运行代码，得到输出结果
        List<ProcessMessage> processMessageList = runFile(file, input);

        //4，收集整理输出结果
        ExecuteCodeResponse executeCodeResponse = getOutput(processMessageList);

        //5,清理文件
        boolean del = deleteFile(file);
        if(!del){
            log.error("清理文件失败,路径:{}",file.getParentFile().getAbsolutePath());
        }
        return executeCodeResponse;
    }

    //发生异常 返回结果
    public ExecuteCodeResponse errorResponse(Throwable e) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        executeCodeResponse.setOutput(new ArrayList<>());
        executeCodeResponse.setMessage(e.getMessage());
        executeCodeResponse.setStatus(3);
        executeCodeResponse.setJudgeInfo(new JudgeInfo());
        return executeCodeResponse;

    }
}
